/*
 * cm_multicast.c
 *
 *  Created on: 2014年9月17日
 *      Author: chens
 */

#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <stdio.h>

#include <cm/net/cm_multicast.h>

struct cm_multicast {
	pthread_t tid;
	int fd; /* 接收组播数据的套接字 */
	struct sockaddr_in addr; /* 组播地址 */
	mcast_proc proc;
};

static inline cm_mcast *mcast_new()
{
	cm_mcast * ptr = NULL;
	ptr = ((cm_mcast *) calloc(1, sizeof(cm_mcast)));

	if (ptr == NULL)
		return NULL;

	ptr->fd = -1;
	return ptr;
}

static inline void mcast_delete(cm_mcast **ptr)
{
	if ((*ptr)->fd >= 0)
		close((*ptr)->fd);
	free(*ptr);
	*ptr = NULL;
}

static void * mcast_thread(void *arg)
{
	cm_mcast *mcast = (cm_mcast *) arg;

	char buffer[1024];

	while (1) {
		int ret = read(mcast->fd, buffer, 1024);
		if (ret <= 0) {
			perror("read");
			sleep(1);
			continue;
		}

		mcast->proc(buffer, ret);
	}

	return NULL;
}

/*
 * @brief	创建一个可用于发送和接受multi cast的对象
 * @param	group_addr: D类IP地址
 * @param	local_addr: 本地接口
 * @param	port: 组播端口？
 * @param	proc: 用户处理组播数据的回调函数
 * @return	成功返回非NULL
 */
cm_mcast * mcast_create(const char *group_addr, const char * local_addr,
		unsigned short port, mcast_proc proc)
{
	cm_mcast *ptr = mcast_new();
	if (ptr == NULL)
		return NULL;

	ptr->proc = proc;

	ptr->fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (ptr->fd < 0) {
		free(ptr);
		return NULL;
	}

	int reuse = 1;
	if (0
			> setsockopt(ptr->fd, SOL_SOCKET, SO_REUSEADDR, &reuse,
					sizeof(int))) {
		mcast_delete(&ptr);
		return NULL;
	}

	struct sockaddr_in localaddr;
	memset(&localaddr, 0, sizeof(localaddr));
	localaddr.sin_family = AF_INET;
	localaddr.sin_port = htons(port);
	localaddr.sin_addr.s_addr = INADDR_ANY;

	if (0
			> bind(ptr->fd, (struct sockaddr *) &localaddr,
					sizeof(localaddr))) {
		mcast_delete(&ptr);
		return NULL;
	}

	struct ip_mreq group;
	group.imr_multiaddr.s_addr = inet_addr(group_addr);
	group.imr_interface.s_addr = inet_addr(local_addr); // 本机接口

	if (0
			> setsockopt(ptr->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
					&group, sizeof(group))) {
		mcast_delete(&ptr);
		return NULL;
	}

	ptr->addr.sin_family = AF_INET;
	ptr->addr.sin_port = htons(port);
	ptr->addr.sin_addr.s_addr = inet_addr(group_addr);

	pthread_create(&(ptr->tid), NULL, mcast_thread, ptr);

	return ptr;
}

/*
 * @brief	销毁由 mcast_create 创建的对象
 */
void mcast_destroy(cm_mcast **m)
{
	pthread_cancel((*m)->tid);
	pthread_join((*m)->tid, NULL);
	// pthread_join()
	mcast_delete(m);
}

/*
 * @brief	将数据组播到网络
 * @param	由 mcast_create 创建的对象
 * @param	data,len: 将要发送的数据及其长度
 */
void mcast_send(cm_mcast *m, const char *data, int len)
{
	if (0
			> sendto(m->fd, data, len, 0,
					(struct sockaddr *) &(m->addr),
					sizeof(m->addr)))
		perror("send");
}
